# This file is part of Neuroinfo Toolkit.
#
# Neuroinfo Toolkit is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Neuroinfo Toolkit is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Neuroinfo Toolkit.  If not, see <http://www.gnu.org/licenses/>.

class BaseException(Exception):
	'''
	Base exception	
	'''
	
	def __init__(self, desc=""):
		'''
		Constructor

		:param desc: Exception description
		:type desc: str
		'''
		if(not isinstance(desc, basestring)):
			raise IllegalArgumentException("Description must be an instance of str")

		desc = desc.strip()

		self._desc = desc

	def getDescription(self):
		'''
		Get exception description

		:rtype: str
		'''
		return self._desc
	
	def getMessage(self):
		'''
		Retrieve exception message
		
		:rtype: str 
		'''		
		return "An exception occurred without a description."

	def __str__(self):
		'''
		Override magic string method

		:rtype: str
		'''
		return self.getMessage()

class UnsupportedException(BaseException):
	'''
	Unsupported exception
	'''

	def __init__(self, obj):
		'''
		Constructor

		:param obj: Object
		:type obj: int, float, str, ...
		'''
		if(obj == None):
			raise IllegalArgumentException("Input parameter cannot be None")
		
		self._obj = obj

	def getObject(self):
		'''
		Get object

		:rtype: int, float, str, ...
		'''
		return self._obj

	def getMessage(self):
		'''
		Get message

		:rtype: str
		'''
		message = "Unsupported operation: type=" + str(type(self._obj).__name__)

		if(hasattr(self._obj, "__name__")):
			message += ", name=" + self._obj.__name__

		return message
		
class IllegalArgumentException(BaseException):
	'''
	Illegal argument exception	
	'''

	def __init__(self, desc):
		'''
		Constructor
		
		:param desc: Description
		:type desc: str
		'''
		BaseException.__init__(self, desc)

		if(self._desc == ""):
			raise IllegalArgumentException("Description cannot be empty")

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		return self._desc

class FileNotFoundException(BaseException):
	'''
	File not found exception	
	'''
	
	def __init__(self, filename):
		'''
		Constructor
		
		:param filename: File name
		:type filename: str
		'''
		BaseException.__init__(self)
		
		if(not isinstance(filename, basestring)):
			raise IllegalArgumentException("Filename must be an instance of str")
		
		filename = filename.strip()
		
		if(filename == ""):
			raise IllegalArgumentException("Filename cannot be empty")
		
		self._filename = filename
		
	def getMessage(self):
		'''
		Get custom message
		
		:rtype: str
		'''
		return "File not found: " + self._filename

class FileExistsException(BaseException):
	'''
	File already exists exception
	'''

	def __init__(self, filename):
		'''
		Constructor

		:param filename: File name
		:type filename: str
		'''
		BaseException.__init__(self)

		if(not isinstance(filename, basestring)):
			raise IllegalArgumentException("Filename must be an instance of str")

		filename = filename.strip()

		if(filename == ""):
			raise IllegalArgumentException("Filename cannot be empty")

		self._filename = filename

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		return "File exists: " + self._filename

class FileExistsException(BaseException):
	'''
	File already exists exception
	'''

	def __init__(self, filename):
		'''
		Constructor

		:param filename: File name
		:type filename: str
		'''
		BaseException.__init__(self)

		if(not isinstance(filename, basestring)):
			raise IllegalArgumentException("Filename must be an instance of str")

		filename = filename.strip()

		if(filename == ""):
			raise IllegalArgumentException("Filename cannot be empty")

		self._filename = filename

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		return "File exists: " + self._filename
			
class IOException(BaseException):
	'''
	I/O exception	
	'''
	READ=1
	WRITE=2
	EXE=3
	DEL=4
	DEL_FULL=5
	
	def __init__(self, type, filename):
		'''
		Constructor

		:param type: IOException.READ, IOException.WRITE, etc.
		:type type: int
		:param filename: File name
		:type filename: str
		'''
		BaseException.__init__(self)

		if(not isinstance(filename, basestring)):
			raise IllegalArgumentException("Filename must be an instance of str")
		elif(not isinstance(type, int)):
			raise IllegalArgumentException("Exception type must be an instance of int")

		filename = filename.strip()

		if(filename == ""):
			raise IllegalArgumentException("Filename cannot be empty")
		elif(type < 1 or type > 5):
			raise IllegalArgumentException("Exception type must be IOException.READ, IOException.WRITE, IOException.EXE, IOException.DEL, or IOException.DEL_FULL")

		self._filename = filename
		self._type = type

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		if(self._type == IOException.READ):
			return "File not readable: " + self._filename
		elif(self._type == IOException.WRITE):
			return "File not writable: " + self._filename
		elif(self._type == IOException.EXE):
			return "File not executable: " + self._filename
		elif(self._type == IOException.DEL):
			return "File cannot be deleted: " + self._filename
		elif(self._type == IOException.DEL_FULL):
			return "Directory is full and will not be deleted: " + self._filename

class CommandNotFoundException(BaseException):
	'''
	Command not found exception	
	'''

	def __init__(self, binary, package):
		'''
		Constructor
		
		:param binary: Binary
		:type binary: str
		:param package: Package
		:type package: str
		'''
		BaseException.__init__(self)

		if(not isinstance(binary, basestring)):
			raise IllegalArgumentException("Binary must be an instance of str")
		elif(not isinstance(package, basestring)):
			raise IllegalArgumentException("Package must be an instance of str")

		binary = binary.strip()
		package = package.strip()

		## --- this might bite me in the bud
		if(binary == ""):
			raise IllegalArgumentException("Binary cannot be empty")

		self._binary = binary
		self._package = package

	def getBinary(self):
		'''
		Get binary that triggered this exception
		
		:rtype: str
		'''
		return self._binary
	
	def getPackage(self):
		'''
		Get package that triggered this exception
		
		:rtype: str
		'''
		return self._package
	
	def getMessage(self):
		'''
		Get custom message
		
		:rtype: str
		'''
		message = "Command not found\n\t--> Binary: " + self._binary
		
		if(self._package != ""):
			message = message + "\n\t--> Hint: " + self._package
			
		return message
		
class FileFormatException(BaseException):
	'''
	File format exception	
	'''

	def __init__(self, filename):
		'''
		Constructor
		
		:param filename: File name
		:type filename: str
		'''
		BaseException.__init__(self)

		if(not isinstance(filename, basestring)):
			raise IllegalArgumentException("Filename must be an instance of str")

		filename = filename.strip()

		if(filename == ""):
			raise IllegalArgumentException("Filename cannot be empty")

		self._filename = filename
		
	def getFilename(self):
		'''
		Get the filename that triggered this exception

		:rtype: str
		'''
		return self._filename

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		return "Incorrect file format: " + self._filename

class InternalException(BaseException):
	'''
	Internal exception	
	'''

	def __init__(self, desc=""):
		'''
		Constructor
		
		:param desc: Description
		:type desc: str
		'''
		BaseException.__init__(self, desc)
		
		if(self._desc == ""):
			raise IllegalArgumentException("Description cannot be empty")
	
	def getMessage(self):
		'''
		Get custom message
		
		:rtype: str
		'''
		return "Internal exception: " + self._desc
	
class IndexOutOfRangeException(BaseException):
	'''
	Index out of range exceptions	
	'''

	def __init__(self, collection, index):
		'''
		Constructor

		:param collection: Collection
		:type collection: int, float, str, ...
		:param index: Index
		:type index: int, float, str, ...
		'''
		BaseException.__init__(self)

		if(collection == None):
			raise IllegalArgumentException("Collection cannot be null")
		elif(index == None):
			raise IllegalArgumentException("Index cannot be null")

		self._collection = collection
		self._index = index

	def getCollection(self):
		'''
		Get collection that triggered this exception
		
		:rtype: int, float, str, ...
		'''
		return self._collection
	
	def getIndex(self):
		'''
		Get index that triggered this exception
		
		:rtype: int, float, str, ...
		'''
		return self._index
	
	def getMessage(self):
		'''
		Get custom message
		
		:rtype: str
		'''
		return "Index (" + str(self._index) + ") out of range for collection of type \"" + type(self._collection).__name__ + "\""
	
class RegularExpressionException(BaseException):
	'''
	Regular expression exception
	'''

	def __init__(self, string, regex, desc=""):
		'''
		Constructor

		:param string: String
		:type string: str
		:param regex: Regular expression
		:type regex: str
		:param desc: Description
		:type desc: str
		'''
		BaseException.__init__(self, desc)

		if(not isinstance(string, basestring)):
			raise IllegalArgumentException("Input string must be an instance of str")
		elif(not isinstance(regex, basestring)):
			raise IllegalArgumentException("Regular expression must be an instance of str")

		string = string.strip()
		regex = regex.strip()

		if(string == ""):
			raise IllegalArgumentException("String cannot be empty")
		elif(regex == ""):
			raise IllegalArgumentException("String cannot be empty")

		self._string = string
		self._regex = regex
	
	def getString(self):
		'''
		Get string that triggered this exception
		
		:rtype: str
		'''
		return self._string
	
	def getRegex(self):
		'''
		Get the regular expression that triggered this exception
		
		:rtype: str
		'''
		return self._regex
	
	def getMessage(self):
		'''
		Get custom message
		
		:rtype: str
		'''
		message = "Regular expression error"
		
		if(self._desc != ""):
			message += self._desc

		message += "\n\n"
		message += "Regex: " + self._regex + "\n"
		message += "String: " + self._string

		return message
		
class EmailException(BaseException):
	'''
	Email exception
	'''

	def __init__(self, settings, desc):
		'''
		Constructor
		
		:param settings: Email settings
		:type settings: dict
		:param desc: Description
		:type desc: str
		'''
		BaseException.__init__(self, desc)

		if(not isinstance(settings, dict)):
			raise IllegalArgumentException("Email settings must be an instance of dict")

		self._settings = settings

	def getSettings(self):
		'''
		Get Email settings that triggered this exception

		:rtype: dict
		'''
		return self._settings

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		return "Email error: " + self._desc

class DimensionMismatchException(BaseException):
	'''
	Dimensionality mismatch exception
	'''

	def __init__(self, struct, desc):
		'''
		Constructor
		
		:param struct: Data structure
		:type struct: int, float, str, ...
		:param desc: Description
		:type desc: str
		'''
		BaseException.__init__(self, desc)
		
		if(struct == None):
			raise IllegalArgumentException("Struct cannot be null")
		
		if(self._desc == ""):
			raise IllegalArgumentException("Description cannot be empty")
		
		self._struct = struct
		
	def getStruct(self):
		'''
		Get the data structure that triggered this exception
		
		:rtype: int, float, str, ...
		'''
		return self._struct
	
	def getMessage(self):
		'''
		Get custom message
		
		:rtype: str
		'''
		return "Dimensionality mismatch: " + self._desc
		
class AuthenticationException(BaseException):
	'''
	Authentication exception
	'''
	def __init__(self, desc=""):
		'''
		Constructor

		:param desc: Description
		:type desc: str
		'''
		BaseException.__init__(self, desc)

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		message = "Authentication failed"

		if(self._desc != ""):
			message += ": " + self._desc

		return message

class EnvironmentException(BaseException):
	'''
	Environment exception
	'''
	NO_VAR=1

	def __init__(self, type, varname):
		'''
		Constructor

		:param type: EnvironmentException.NO_VAR, etc.
		:type type: int
		:param varname: Variable name
		:type varname: str
		'''
		BaseException.__init__(self)

		if(not isinstance(varname, basestring)):
			raise IllegalArgumentException("Variable name must be an instance of str")
		elif(not isinstance(type, int)):
			raise IllegalArgumentException("Exception type must be an instance of int")

		varname = varname.strip()

		if(varname == ""):
			raise IllegalArgumentException("Variable name cannot be empty")
		elif(type != 1):
			raise IllegalArgumentException("Exception type must be EnvironmentException.NO_VAR")

		self._varname = varname
		self._type = type

	def getMessage(self):
		'''
		Get custom message

		:rtype: str
		'''
		if(self._type == EnvironmentException.NO_VAR):
			return "Environment variable does not exist: " + self._varname